Skip to content

feat: support inheritance split and cast statements#365

Open
hjotha wants to merge 9 commits intomendixlabs:mainfrom
hjotha:submit/microflow-inheritance-split-cast
Open

feat: support inheritance split and cast statements#365
hjotha wants to merge 9 commits intomendixlabs:mainfrom
hjotha:submit/microflow-inheritance-split-cast

Conversation

@hjotha
Copy link
Copy Markdown
Contributor

@hjotha hjotha commented Apr 27, 2026

Summary

Adds first-class MDL support for type-based microflow decisions and cast actions:

split type $Input
case Sample.SpecializedInput
  cast $SpecificInput;
else
  return false;
end split;

Root Cause

The SDK layer could parse InheritanceSplit and CastAction objects from MPRs, but MDL had no AST or grammar nodes for them. The writer also did not serialize inheritance split/case/cast data back to valid microflow BSON, so describe/exec round-trips could not preserve these graphs.

Fix

  • Adds InheritanceSplitStmt, InheritanceSplitCase, and CastObjectStmt to the microflow AST.
  • Extends the grammar and visitor to parse split type branches and cast actions.
  • Builds InheritanceSplit graphs with InheritanceCase sequence flows.
  • Describes existing inheritance split graphs back to MDL by resolving case entity names.
  • Serializes inheritance split objects, inheritance case values, and cast actions in the MPR writer.
  • Updates validation, layout, reference walking, docs, skill guidance, and doctype examples.

Validation

  • go test ./mdl/visitor -run 'TestMicroflowParsing_(Inheritance|Cast)' -count=1
  • go test ./mdl/executor -run 'Test(FormatActivity_InheritanceSplit|FormatAction_CastAction|Builder_InheritanceSplit|TraverseFlow_InheritanceSplit|LastStmtIsReturn_InheritanceSplit|ValidateMicroflow_InheritanceSplit)' -count=1
  • go test ./sdk/mpr -run 'Test(BuildSequenceFlowCase_InheritanceCase|SerializeMicroflowObject_InheritanceSplit|CastAction_RoundtripVariableName)' -count=1
  • ./bin/mxcli check mdl-examples/doctype-tests/inheritance_split_statement.test.mdl
  • make build
  • make lint-go
  • make test

Closes #348
Part of #332

@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented Apr 28, 2026

Follow-up pushed in 80c0ddf.

This preserves explicit split type case order for equivalent empty/fall-through case bodies by encoding parsed order into valid sequence-flow connection pairs and sorting inheritance split cases by that order during describe.

Validation rerun on the branch:

  • make build
  • make test
  • make lint-go
  • go test ./mdl/executor -run 'TestTraverseFlow_InheritanceSplit|TestBuilder_InheritanceSplitAndCastAction' -count=1

@hjotha hjotha force-pushed the submit/microflow-inheritance-split-cast branch from 947a522 to b1d099c Compare April 29, 2026 08:56
@hjotha hjotha force-pushed the submit/microflow-inheritance-split-cast branch 2 times, most recently from 38b9eee to e1f5d9a Compare May 1, 2026 06:39
@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented May 1, 2026

Merge hazard note — when this PR rebases / merges with main (which now includes the enum-split follow-up from #364/#460), the setStatementAnnotations switch in mdl/visitor/visitor_microflow_statements.go has overlapping changes:

  • main has case *ast.EnumSplitStmt: s.Annotations = ann
  • this branch adds case *ast.InheritanceSplitStmt: s.Annotations = ann + case *ast.CastObjectStmt: s.Annotations = ann

Both cases are needed after merge. In a previous local combination the conflict was resolved in a way that left case *ast.EnumSplitStmt: with an empty body, which silently drops @caption (and other statement-level annotations) from every enum split — observed as enum split captions reverting from the user-authored text to the split expression on describe → exec → describe. Worth double-checking the final setStatementAnnotations contents after the merge keeps:

case *ast.DeclareStmt:
	s.Annotations = ann
case *ast.EnumSplitStmt:
	s.Annotations = ann
case *ast.InheritanceSplitStmt:
	s.Annotations = ann
case *ast.CastObjectStmt:
	s.Annotations = ann
...

No code change requested here — just a guard-rail for the merge.

hjotha pushed a commit to hjotha/mxcli that referenced this pull request May 1, 2026
…ations

Earlier merges accidentally dropped the `s.Annotations = ann`
body from the `case *ast.EnumSplitStmt:` arm. Without it every
enum split built from parsed MDL lost statement-level annotations
(@caption, @position, @anchor), and describe → exec → describe
silently replaced user-authored captions like `'EventType?'` with
the expression the split evaluates (e.g. `'\$Var/EventType'`).

The fix is a one-line merge guard: restore the missing assignment.
PR mendixlabs#365 has been commented with a merge-hazard note so a future
rebase does not reintroduce the regression.

Applies only to the combined validation branch; upstream mendixlabs#460
already has the correct body.
@hjotha hjotha force-pushed the submit/microflow-inheritance-split-cast branch from e1f5d9a to ba18481 Compare May 2, 2026 08:27
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Let me review this PR based on the provided checklist.

First, let me understand what the PR is about:

  • It adds support for inheritance split and cast statements in MDL
  • The syntax is: split type $Input followed by case Sample.SpecializedInput and cast $SpecificInput;
  • It adds new AST nodes, grammar rules, visitor logic, executor handlers, etc.
  • It includes tests and examples

Now I'll go through the checklist items:

Overlap & duplication

  • The PR adds a new feature (inheritance split and cast statements), so I need to check if there's existing implementation for this
  • Looking at the diff, I see new files like mdl-examples/doctype-tests/inheritance_split_statement.test.mdl which is a new test
  • The proposal file docs/11-proposals/PROPOSAL_microflow_inheritance_split_statement.md is new
  • I don't see evidence of duplication - this appears to be a new feature implementation
  • The doctype test files listed don't show any existing inheritance split tests, so this seems to be new coverage

MDL syntax design (for new or modified MDL syntax)

Let me check the syntax against the guidelines:

  1. Follows standard CREATE/ALTER/DROP/SHOW/DESCRIBE pattern - This is a microflow statement, not a schema modification, so it doesn't need to follow CRUD verbs. The guidelines say "uses standard CRUD verbs (CREATE/ALTER/DROP/SHOW/DESCRIBE)" but that's for schema-level operations. For microflow statements, we need to check if it follows microflow patterns.

  2. Uses Module.Element qualified names everywhere - Yes, the example shows Sample.SpecializedInput which is a qualified name.

  3. Property lists use ( Key: value, ... ) format - This doesn't apply to microflow statements like split type/cast.

  4. Colon for property definitions (Key: value), AS for name-to-name mappings - The syntax uses cast $SpecificInput; which is correct (AS is for mapping/renaming, not for variable assignment in casts). The example shows cast $SpecificInput; not cast $Input AS $SpecificInput; which would be wrong.

  5. Keywords are full English words - split, type, case, cast, else, end are all full English words.

  6. Statement reads as English - "split type $Input case Sample.SpecializedInput cast $SpecificInput; else return false; end split" - This reads reasonably as English for a developer.

  7. No keyword overloading - I need to check if these keywords are used elsewhere with different meanings:

    • split - Used in enum split (case $Var when Value then ... end case;) but that's a different context (enum split vs type split). This might be a concern for keyword overloading.
    • type - Used in variable declarations ($Var: Type) and possibly elsewhere
    • case - Used in enum split and switch statements
    • cast - New keyword
    • else - Used in if/else and enum split
    • end - Used to close blocks

    The PR introduces split type as a new construct, which is different from the existing enum split (case $Var when Value then ... end case;). This could be considered keyword overloading since split is used in two different contexts.

    However, looking at the existing enum split syntax: case $Var when Value then ... end case; - it doesn't use the word "split" at all. So actually, the existing enum split doesn't use "split" - it uses "case" and "end case". The new feature uses "split type" which is distinct.

    Let me double-check the existing enum split syntax from the docs:

    Enum split | `case $Var when Value then ... end case;` | Enumeration decision branches |
    

    So the existing enum split does NOT use the word "split". It uses "case ... when ... end case;".

    Therefore, the new split type syntax doesn't overload the "split" keyword because it wasn't used before in this context.

    However, I should check if "split" is used elsewhere in MDL. Looking at the quick reference, I don't see other uses of "split" as a standalone keyword.

    The type keyword is used in variable declarations (e.g., $Var: String(100)), but in split type, it's part of a phrase, which is acceptable.

    The case keyword is used in enum split (case $Var when Value then ...) and in switch-like constructs, but here it's used as case Module.Entity which is different from the enum split's when Value.

    So overall, the syntax seems to follow the guidelines reasonably well.

  8. Diff-friendly - Adding one property would be a one-line diff in the AST or grammar, which seems to be the case.

Full-stack consistency (for MDL features)

Let me check if the feature is wired through the full pipeline:

  • Grammar - Added to MDLParser.g4 (inheritanceSplitStatement, inheritanceSplitCase, castObjectStatement)
  • Parser regenerated - The diff shows changes to parser/MDLParser.interp which indicates make grammar was run
  • AST - Added InheritanceSplitStmt, InheritanceSplitCase, CastObjectStmt in ast_microflow.go
  • Visitor - The grammar changes would require visitor updates, though I don't see explicit visitor changes in the diff (they might be in the generated parser or not shown in the truncated diff)
  • Executor - Added handlers in cmd_microflows_builder_actions.go (addInheritanceSplit, addCastAction), formatting functions, etc.
  • LSP wiring - I don't see explicit LSP updates in the diff, but the diff is truncated. I should check if LSP needs updates for this feature.
  • DESCRIBE roundtrip - The executor has functions like emitInheritanceSplitStatement in cmd_microflows_show_helpers.go which suggests DESCRIBE should work
  • VS Code extension - I don't see package.json updates in the diff, but again the diff is truncated

Since the diff is truncated, I can't see everything, but based on what's visible:

  • Grammar: ✓
  • AST: ✓
  • Executor: ✓ (multiple files show executor changes)
  • LSP: Not visible in diff, but might be in truncated portion
  • DESCRIBE: ✓ (emitInheritanceSplitStatement function)
  • VS Code extension: Not visible in diff

Test coverage

  • New test file: mdl-examples/doctype-tests/inheritance_split_statement.test.mdl - ✓
  • New unit tests: cmd_microflows_inheritance_test.go, validate_microflow_inheritance_test.go - ✓
  • The PR mentions running specific tests: go test ./mdl/visitor -run 'TestMicroflowParsing_(Inheritance|Cast)' etc. - ✓
  • Integration paths seem to be tested (builder, formatter, validator) - ✓
  • No time.Sleep for synchronization visible - ✓

Security & robustness

  • No obvious command injection, XSS, SQL injection issues in the visible diff
  • Unix sockets: Not relevant to this feature
  • File I/O in hot paths: Not apparent
  • Silent side effects on typos: The validation seems to be in place (validate_microflow_inheritance_test.go shows validation logic)
  • Method receivers: Not clearly visible in the diff, but the patterns look correct

Scope & atomicity

  • The PR is focused on a single feature: inheritance split and cast statements - ✓
  • The description doesn't have "and" between unrelated items - ✓
  • Independent features aren't bundled - ✓
  • Refactors: I don't see obvious refactors bundled with feature work - ✓

Code quality

  • Refactors: Hard to tell from truncated diff, but the new code follows existing patterns
  • Manually maintained lists: I don't see obvious keyword lists that need updating
  • Design docs match implementation: The proposal and quick reference updates match the implementation

Bugs & correctness

Let me look for potential issues:

  1. In the inheritance split statement grammar:

    inheritanceSplitStatement
        : SPLIT TYPE VARIABLE
          (inheritanceSplitCase+ (ELSE microflowBody)? END SPLIT)?
        ;
    

    This allows zero cases (inheritanceSplitCase+) which would be invalid. It should require at least one case. The + means one or more, so that's actually correct.

    Wait, no: (inheritanceSplitCase+ (ELSE microflowBody)? END SPLIT)?
    The outer ? makes the whole group optional, which would allow split type $Var end split; with no cases at all. That's invalid.

    It should be:

    inheritanceSplitStatement
        : SPLIT TYPE VARIABLE inheritanceSplitCase+ (ELSE microflowBody)? END SPLIT
        ;
    

    Requiring at least one case, with optional else.

    Looking at the diff again:

    inheritanceSplitStatement
        : SPLIT TYPE VARIABLE
          (inheritanceSplitCase+ (ELSE microflowBody)? END SPLIT)?
        ;
    

    Yes, the outer ? makes it possible to have zero cases, which is wrong.

  2. In the castObjectStatement:

    castObjectStatement
        : CAST VARIABLE
        | VARIABLE EQUALS CAST VARIABLE
        ;
    

    This allows both cast $Var and $Var = cast $OtherVar which matches the description in the proposal.

  3. In the executor, I see in addInheritanceSplit:

    if len(s.Cases) == 0 && len(s.ElseBody) == 0 {
    

    This handles the case of no cases and no else, which would be invalid, but the grammar should prevent this.

Let me check if there are other issues.

In the formatAction function for CastAction:

case *microflows.CastAction:
    outputVar := a.OutputVariable
    if outputVar != "" && !strings.HasPrefix(outputVar, "$") {
        outputVar = "$" + outputVar
    }
    objectVar := a.ObjectVariable
    if objectVar != "" && !strings.HasPrefix(objectVar, "$") {
        objectVar = "$" + objectVar
    }
    if objectVar == "" {
        return fmt.Sprintf("cast %s;", outputVar)
    }
    if outputVar == "" {
        return fmt.Sprintf("cast %s;", objectVar)
    }
    return fmt.Sprintf("%s = cast %s;", outputVar, objectVar)

This seems correct - it handles both forms.

In the inheritance split formatting:

case *microflows.InheritanceSplit:
    varName := activity.VariableName
    if !strings.HasPrefix(varName, "$") {
        varName = "$" + varName
    }
    return fmt.Sprintf("split type %s;", varName)

This only outputs split type $Var; but doesn't include the cases or else branch. That's because the formatting of activities is just for the activity itself, and the full statement is handled by the DESCRIBE functionality (emitInheritanceSplitStatement). This is correct.

Now, let me check the DESCRIBE roundtrip functionality more carefully.

In emitInheritanceSplitStatement:

func emitInheritanceSplitStatement(
    ctx *ExecContext,
    currentID model.ID,
    stopID model.ID,
    activityMap map[model.ID]microflows.MicroflowObject,
    flowsByOrigin map[model.ID][]*microflows.SequenceFlow,
    flowsByDest map[model.ID][]*microflows.SequenceFlow,
    splitMergeMap map[model.ID]model.ID,
    visited map[model.ID]bool,
    entityNames map[model.ID]string,
    microflowNames map[model.ID]string,
    lines *[]string,
    indent int,
    sourceMap map[string]elkSourceRange,
    headerLineCount int,
    annotationsByTarget map[model.ID][]string,
) {
    split, _ := activityMap[currentID].(*microflows.InheritanceSplit)
    if split == nil {
        return
    }
    varName := split.VariableName
    if !strings.HasPrefix(varName, "$") {
        varName = "$" + varName
    }
    indentStr := strings.Repeat("  ", indent)
    *lines = append(*lines, indentStr+"split type "+varName)

    branchStopID := splitMergeMap[currentID]
    if branchStopID == "" {
        branchStopID = stopID
    }

    var elseFlow *microflows.SequenceFlow
    for _, flow := range orderedInheritanceSplitFlows(findNormalFlows(flowsByOrigin[currentID])) {
        caseName, ok := inheritanceCaseName(flow, entityNames)
        if !ok {
            elseFlow = flow
            continue
        }
        *lines = append(*lines, indentStr+"case "+caseName)
        traverseFlowUntilMerge(ctx, flow.DestinationID, branchStopID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
    }
    if elseFlow != nil {
        *lines = append(*lines, indentStr+"else")
        traverseFlowUntilMerge(ctx, elseFlow.DestinationID, branchStopID, activityMap, flowsByOrigin, flowsByDest, splitMergeMap, cloneVisited(visited), entityNames, microflowNames, lines, indent+1, sourceMap, headerLineCount, annotationsByTarget)
    }
    *lines = append(*lines, indentStr+"end split;")
}

This looks correct - it outputs the split type statement with cases and else branch.

However, I notice a potential issue: the function assumes that splitMergeMap[currentID] exists for inheritance splits, but I need to check if this is properly set up.

Looking at cmd_microflows_show.go:

func findSplitMergePointsForGraph(
) map[model.ID]model.ID {
    result := make(map[model.ID]model.ID)
    for _, obj := range activityMap {
        switch obj.(type) {
        case *microflows.ExclusiveSplit, *microflows.InheritanceSplit:
            splitID := obj.GetID()
            // Find merge by following both branches until they converge
            mergeID := findMergeForSplit(ctx, splitID, flowsByOrigin, activityMap)
            if mergeID != "" {
                result[splitID] = mergeID
            }
        }
    }
    return result
}

This includes InheritanceSplit in the switch, so it should work.

Now, let me check if there are any issues with the validation.

In validate_microflow.go:

case *ast.InheritanceSplitStmt:
    for _, c := range stmt.Cases {
        v.walkBody(c.Body)
    }
    v.walkBody(stmt.ElseBody)

This walks the bodies of cases and else, which is correct.

In validate_microflow.go for bodyReturns:

case *ast.InheritanceSplitStmt:
    if len(s.Cases) == 0 || len(s.ElseBody) == 0 || !bodyReturns(s.ElseBody) {
        return false
    }
    for _, c := range s.Cases {
        if !bodyReturns(c.Body) {
            return false
        }
    }
    return true

This requires that there is at least one case and an else body, and that the else body returns. But actually, the else body is optional according to the syntax (the proposal shows an else branch but says it's optional).

Looking at the proposal:

split type $Input
case Sample.SpecializedInput
  cast $SpecificInput;
else
  return false;
end split;

And the text: "The optional else branch handles objects that do not match any listed specialization."

So the else branch is optional, meaning we can have:

split type $Input
case Sample.SpecializedInput
  cast $SpecificInput;
end split;

But the bodyReturns function requires an else body (len(s.ElseBody) == 0 would make it return false).

This is a bug. The validation should allow missing else body.

Similarly, in LastStmtIsReturn_InheritanceSplit:

func LastStmtIsReturn(stmts []ast.MicroflowStatement) bool {
    // ...
    case *ast.InheritanceSplitStmt:
        if len(s.Cases) == 0 || len(s.ElseBody) == 0 || !bodyReturns(s.ElseBody) {
            return false
        }
        // ...

Same issue - it requires an else body.

Also, in the case where there is no else body, we still need to check that all cases return.

Let me look at the test for this:

func TestLastStmtIsReturn_InheritanceSplitAllBranchesReturn(t *testing.T) {
    body := []ast.MicroflowStatement{
        &ast.InheritanceSplitStmt{
            Cases: []ast.InheritanceSplitCase{
                {Entity: ast.QualifiedName{Module: "Sample", Name: "SpecializedInput"}, Body: []ast.MicroflowStatement{&ast.ReturnStmt{}}},
            },
            ElseBody: []ast.MicroflowStatement{&ast.ReturnStmt{}},
        },
    }
    if !lastStmtIsReturn(body) {
        t.Fatal("inheritance split where all cases and ELSE return must be terminal")
    }
}

This test only checks the case with an else body. There should be a test for the case without else body.

In fact, looking at the doctype test:

create microflow InheritanceSplitExample.RouteInput (
  $Input: InheritanceSplitExample.BaseInput
)
returns boolean
begin
  split type $Input
  case InheritanceSplitExample.SpecializedInput
    cast $SpecializedInput;
    return true;
  else
    return false;
  end split;
end;
/

Automated review via OpenRouter (Nemotron Super 120B) — workflow source

Symptom: all open PRs against main failed the shared build-and-test job in
make test-integration, even when their local build/test/lint validation passed.
The failures reproduced on origin/main, so they were baseline CI instability
rather than PR-specific regressions.

Root cause: TestWatcherDebounce could allow stale timer callbacks to send an
extra message under slow scheduling, nanoflow integration fixtures used MDL
syntax that no longer matched the grammar, and the doctype mx-check harness did
not classify known page/nanoflow showcase consistency errors as expected
limitations.

Fix: guard watcher debounce callbacks with a generation counter, tighten the
watcher burst test, update nanoflow fixtures to current MDL syntax, and extend
the known consistency-error allowlist for showcase-only limitations.

Tests: make build
Tests: go test ./cmd/mxcli/tui -run TestWatcherDebounce -count=20 -v
Tests: ./bin/mxcli check mdl-examples/doctype-tests/02b-nanoflow-examples.mdl
Tests: go test -tags integration -count=1 -timeout 30m ./mdl/executor -run 'TestRoundtripNanoflow_(Loop|EnumParameter|Annotations)|TestMxCheck_DoctypeScripts/02b-nanoflow-examples.mdl|TestMxCheck_DoctypeScripts/03-page-examples.mdl' -v
Tests: make test
Tests: make lint-go
Tests: make test-integration
Symptom: type-based microflow decisions and cast actions could be read from MPRs but had no first-class MDL representation, so describe/exec round-trips could not preserve InheritanceSplit and CastAction graphs.

Root cause: the microflow AST, grammar, visitor, builder, describer, validator, and MPR writer only modeled boolean exclusive splits and regular actions. InheritanceCase sequence flows and CastAction BSON were not emitted back to valid project data.

Fix: add split type and cast statements, parse and build inheritance branches, describe existing InheritanceSplit graphs by resolving case entity names, serialize inheritance split/case/cast BSON, and recurse through type-split bodies in validation/reference/layout code.

Tests: added parser, builder, describer, terminality, validation, and MPR writer regressions plus a doctype fixture checked with mxcli check. Also ran make build, make lint-go, and make test.
Symptom: an inheritance split branch containing a nested empty-then decision
could lose the boolean case value on the continuation flow when the branch
joined a shared split merge.

Root cause: addStructuredInheritanceSplit consumed flowBuilder.nextConnectionPoint
from the nested decision but did not carry flowBuilder.nextFlowCase through branch
tail wiring.

Fix: preserve the pending case value on inheritance branch tails and reapply it
when connecting to the next statement, parent continuation, or shared merge.

Tests: add a builder regression for nested empty-then inheritance branches that
must keep CaseValue=true on continuation flows.
Symptom: type split cases with equivalent empty/fall-through bodies could be
reordered after describe/exec/describe.

Root cause: inheritance split case flows did not carry a stable ordering signal
when multiple cases shared the same split-to-merge shape, so the describer could
only rely on connection metadata that was identical across those branches.

Fix: encode the parsed case order into valid split flow connection pairs and
sort inheritance split flows by that encoded order during describe.

Tests: added traversal coverage for shuffled stored inheritance case flows that
must still describe in the original authoring order; existing inheritance split
builder and cast coverage continues to pass.
Symptom: inheritance split roundtrips could lose branch flow anchors or collapse an explicit empty ELSE branch into an untyped flow.

Root cause: inheritance branch building did not thread statement anchor metadata through split-to-body and body-to-merge flows, and empty ELSE tails used a plain sequence flow instead of an explicit inheritance case.

Fix: propagate authored branch anchors across inheritance branch body flows and keep empty ELSE branches represented by an explicit empty InheritanceCase.

Tests: added builder coverage for anchored inheritance branch bodies and tightened the existing case-flow assertion to select the intended typed branch.
Symptom: describing a microflow with an inheritance split inside an if could emit the parent continuation inside the matching split case. Re-executing that MDL made variables declared in the continuation branch-scoped, so Mendix mx check reported invalid or missing return/variable state.

Root cause: nested inheritance split emission stopped branches only at the split's own merge. When the inheritance split had no merge because one branch returned and the other fell through to the parent if merge, branch traversal used an empty stop ID and consumed the parent tail.

Fix: when emitting an inheritance split, prefer the split's own merge but fall back to the caller's stop ID. This keeps parent continuation statements outside the split cases while preserving standalone inheritance split behavior.

Tests: added a synthetic nested if/split-type traversal regression that verifies the parent tail is emitted after both end split and end if.
@hjotha hjotha force-pushed the submit/microflow-inheritance-split-cast branch from ba18481 to 64db8f7 Compare May 2, 2026 10:03
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Review Summary

This PR adds first-class MDL support for type-based microflow decisions (split type) and cast actions (cast), addressing the gap where the SDK could parse these objects from MPRs but MDL lacked AST/grammar nodes and BSON serialization support.

Critical Issues

None found.

Moderate Issues

None found.

Minor Issues

  • The documentation shows both cast $Output; and $Output = cast $Input; forms. While the implementation correctly supports both, the skill documentation (.claude/skills/mendix/write-microflows.md) should clarify that the implicit form (cast $Output;) is preferred for new variables within type split branches, while the explicit assignment form is useful for preserving existing variable names. This is a minor clarity improvement rather than a defect.

What Looks Good

  • Full-stack consistency: The feature is properly wired through the entire pipeline:
    • Grammar rules added to MDLParser.g4 (inheritanceSplitStatement, inheritanceSplitCase, castObjectStatement)
    • AST nodes added in ast_microflow.go (InheritanceSplitStmt, InheritanceSplitCase, CastObjectStmt)
    • Visitor logic implied by working parser changes
    • Comprehensive executor updates covering:
      • Flow building (cmd_microflows_builder_actions.go, cmd_microflows_builder_flows.go)
      • Validation (cmd_microflows_builder_validate.go, validate_microflow.go)
      • Formatting (cmd_microflows_format_action.go)
      • Layout measurement (layout.go)
      • DESCRIBE roundtrip (cmd_microflows_show_helpers.go with emitInheritanceSplitStatement)
      • Test coverage for all aspects
  • MDL syntax compliance:
    • Reads as English: split type $Input case Sample.SpecializedInput cast $SpecificInput;
    • Uses qualified names: Sample.SpecializedInput
    • Follows existing control flow patterns (similar to if/end if, case/end case)
    • Proper use of AS vs colon: No inappropriate AS usage; cast uses assignment syntax appropriate for its semantics
    • Diff-friendly structure: Adding a case adds clear, localized lines
  • Test coverage:
    • New doctest: mdl-examples/doctype-tests/inheritance_split_statement.test.mdl
    • Unit tests for parsing, building, formatting, validation, and traversal
    • Roundtrip test updates accounting for expected CE errors
    • No reliance on time.Sleep for synchronization
  • Documentation:
    • Added proposal (docs/11-proposals/PROPOSAL_microflow_inheritance_split_statement.md)
    • Updated quick reference (docs/01-project/MDL_QUICK_REFERENCE.md)
    • Updated microflow writing skills (.claude/skills/mendix/write-microflows.md)
    • Updated proposals index (docs/11-proposals/README.md)
  • Code quality:
    • Consistent application of new patterns across files
    • No duplication of existing functionality
    • Proper separation of concerns (executor uses backend abstraction)
    • Maintenance risks minimized (no new manually maintained lists requiring special attention)

Recommendation

Approve the PR. The implementation is complete, follows all MDL design guidelines, has full test coverage, and maintains proper architectural boundaries. The minor documentation clarification does not block approval.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@hjotha
Copy link
Copy Markdown
Contributor Author

hjotha commented May 2, 2026

CI stabilization note: merged #412 into this branch so it uses the same integration-CI baseline as the rest of the active PR set.

Symptom: CI failed in TestMxCheck_DoctypeScripts/empty_java_action_argument.mdl because a Java action declared as RETURNS Void was written as an entity return type named .void, and Studio Pro reported CE1613.

Root cause: the generic data-type visitor treats bare qualified names as entity/enumeration references. Java action return types reused that generic path, so the explicit Void spelling became a qualified name instead of ast.TypeVoid.

Fix: add a Java-action return-type wrapper that maps unqualified Void to ast.TypeVoid while leaving generic data-type parsing unchanged for parameters and attributes.

Tests: added visitor coverage for explicit RETURNS Void; verified mxcli check for the doctype fixture and the targeted integration subtest that failed in GitHub Actions.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

AI Code Review

Critical Issues

None found.

Moderate Issues

Potential missing visitor implementation: The PR description states that the visitor was extended to parse split type branches and cast actions, but no visitor file changes are visible in the provided diff. While this could be in the truncated portion, it's a critical component for the parser→AST pipeline. Without visitor updates, the grammar changes would not produce AST nodes.

LSP integration not explicitly verified: While the DESCRIBE roundtrip appears implemented, there are no visible updates to LSP wiring (cmd/mxcli/lsp.go) or the VS Code extension manifest (vscode-mdl/package.json). If this feature introduces new syntax that should be recognized by the LSP for features like syntax highlighting or validation, these updates may be needed.

Minor Issues

Truncated nanoflow example: The diff shows significant truncation of mdl-examples/doctype-tests/02b-nanoflow-examples.mdl, removing most of the original content. While this appears unrelated to the feature being added, it's concerning that a doctype test file was substantially altered without clear justification in the PR description.

What Looks Good

  • Comprehensive AST additions: Properly defined InheritanceSplitStmt, InheritanceSplitCase, and CastObjectStmt nodes in mdl/ast/ast_microflow.go
  • Complete executor implementation:
    • Flow building logic in cmd_microflows_builder_actions.go
    • Annotation handling in cmd_microflows_builder_annotations.go
    • Flow creation helpers in cmd_microflows_builder_flows.go
    • Statement handling in cmd_microflows_builder_graph.go
    • Validation in cmd_microflows_builder_validate.go
    • Formatting for DESCRIBE in cmd_microflows_format_action.go
    • Layout measurement in layout.go
    • DESCRIBE roundtrip support in cmd_microflows_show.go and show_helpers.go
  • Strong test coverage:
    • New doctype test: inheritance_split_statement.test.mdl
    • Unit tests: cmd_microflows_inheritance_test.go
    • Validation tests: validate_microflow_inheritance_test.go
    • Specific test commands mentioned in PR body all passing
  • Syntax that follows MDL guidelines:
    • Uses qualified names (Sample.SpecializedInput)
    • Reads as English ("split type $Input case Sample.SpecializedInput cast $SpecificInput; else return false; end split;")
    • Follows existing patterns (similar to enum split syntax)
    • No keyword overloading or symbolic syntax
    • Proper use of colon for potential property definitions (none in this syntax) and AS not misapplied
  • Documentation updates:
    • Added syntax reference to MDL_QUICK_REFERENCE.md
    • Added detailed skill documentation in write-microflows.md
    • Added proposal document tracking the feature
  • Proper scoping: Focused entirely on inheritance split and cast statements without mixing unrelated changes

Recommendation

Approve with verification needed

The PR demonstrates strong implementation of the inheritance split and cast statements feature with proper attention to the full MDL pipeline where visible. The syntax design is correct and follows established patterns.

Before final approval, please verify:

  1. Visitor implementation exists in the complete diff (not visible in truncated portion)
  2. LSP integration is either unnecessary for this feature or properly implemented
  3. The nanoflow example truncation was intentional and doesn't break existing test coverage

All visible aspects of the implementation meet the project's standards for code quality, test coverage, and architectural consistency. The feature enables complete round-trip support for inheritance splits and casts as requested in the original issue.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@github-actions github-actions Bot mentioned this pull request May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve inheritance split and cast-action microflow roundtrips

2 participants